import { dom, Nodes } from '@knno/dom';
import { filesStyle } from './files.css';
import { FormItem } from '../interface';
import { DATA } from '../symbol';
import SVGFile from '@mdi/svg/svg/file.svg';
import SVGImage from '@mdi/svg/svg/file-image.svg';
import SVGWord from '@mdi/svg/svg/file-word.svg';
import SVGExcel from '@mdi/svg/svg/file-excel.svg';
import SVGPowerPoint from '@mdi/svg/svg/file-powerpoint.svg';
import SVGPDF from '@mdi/svg/svg/file-cad.svg';
import SVGVideo from '@mdi/svg/svg/file-video.svg';
import SVGAudio from '@mdi/svg/svg/file-music.svg';
import SVGText from '@mdi/svg/svg/file-document.svg';
import SVGZip from '@mdi/svg/svg/file-export.svg';
import SVGRemove from '@material-design-icons/svg/filled/delete.svg';
import SVGAdd from '@material-design-icons/svg/filled/add.svg';
import { readAsDataURL, Uploader } from '../upload';
import { takeIf } from '../../tools/sugar';

export interface FileItem {
	type: 'server' | 'client';
}

export interface ServerFileItem extends FileItem {
	type: 'server';
	id: string;
	name: string;
	url: string;
	length?: number;
}

export interface ClientFileItem extends FileItem {
	type: 'client';
	file: File;
	url?: string;
}

function isClientItem(item: FileItem): item is ClientFileItem {
	return item.type === 'client';
}

function getFileName(item: FileItem): string {
	if (isClientItem(item)) {
		return item.file.name;
	} else {
		return (item as ServerFileItem).name;
	}
}

async function getFileUrl(item: FileItem): Promise<string> {
	if (isClientItem(item)) {
		if (item.url) return item.url;
		item.url = await readAsDataURL(item.file);
		return item.url;
	} else {
		return (item as ServerFileItem).url;
	}
}

const IMAGE_EXT = /\.(png|jpg|jpeg|gif|bmp|tiff?|svg|webp)$/i;
const WORD_EXT = /\.(doc|docx|rft|docm)$/i;
const EXCEL_EXT = /\.(xls|xlsx|xlsm|csv)$/i;
const PPT_EXT = /\.(ppt|pptx|ppat)$/i;
const TXT_EXT = /\.(txt|log)$/i;
const PDF_EXT = /\.pdf$/i;
const ZIP_EXT = /\.(zip|rar|gz|tar|xz)$/i;
const AUDIO_EXT = /\.(mp3|aac|wav|mid)$/i;
const VIDEO_EXT = /\.(mp4|3gpp|ogg)$/i;

export function getFileIcon(item: FileItem): Nodes {
	let fileName: string;
	if (item.type === 'client') {
		fileName = (item as ClientFileItem).file.name;
	} else {
		fileName = (item as ServerFileItem).name;
	}
	if (IMAGE_EXT.test(fileName)) {
		return dom.html(SVGImage);
	} else if (WORD_EXT.test(fileName)) {
		return dom.html(SVGWord);
	} else if (EXCEL_EXT.test(fileName)) {
		return dom.html(SVGExcel);
	} else if (PPT_EXT.test(fileName)) {
		return dom.html(SVGPowerPoint);
	} else if (TXT_EXT.test(fileName)) {
		return dom.html(SVGText);
	} else if (PDF_EXT.test(fileName)) {
		return dom.html(SVGPDF);
	} else if (ZIP_EXT.test(fileName)) {
		return dom.html(SVGZip);
	} else if (AUDIO_EXT.test(fileName)) {
		return dom.html(SVGAudio);
	} else if (VIDEO_EXT.test(fileName)) {
		return dom.html(SVGVideo);
	} else {
		return dom.html(SVGFile);
	}
}

export type FilesType = 'icon' | 'list';

export class Files extends Nodes implements FormItem {
	private [DATA]: FileItem[] = [];
	constructor() {
		super(dom.div(filesStyle));
		this.attr('type', 'icon');
		this.render();
	}

	private plusButton(): Nodes {
		if (!this.readonly() && !this.disabled() && this[DATA].length < (this.max() ?? Number.MAX_VALUE)) {
			return dom
				.div('add')
				.html(SVGAdd)
				.on('click', async () => {
					const uploader = new Uploader();
					uploader.multple(true);
					try {
						let files = await uploader.select();
						if (files.length > 0) {
							const max = this.max();
							if (typeof max === 'number' && max > 0) {
								files = files.slice(0, max - this[DATA].length);
							}
							this[DATA].push(...files.map((f) => ({ type: 'client', file: f } as FileItem)));
							this.render();
							this.emit(new Event('change'));
						}
					} catch (e) {
						// do nothing...
					}
				});
		} else {
			return new Nodes([]);
		}
	}

	private render() {
		let dragIdx: number | null = null;
		this.html('').append(
			...this[DATA].map((item, idx) => {
				let icon: Nodes;
				const name = getFileName(item);
				if (IMAGE_EXT.test(name)) {
					icon = dom.img().attr('draggable', 'false');
					getFileUrl(item).then((url) => {
						icon.attr('src', url);
					});
				} else {
					icon = dom.div('icon').append(getFileIcon(item));
				}
				const div = dom.div().attr('title', name).append(icon, dom.div('name').text(name));
				if (!this.readonly() && !this.disabled()) {
					div.attr('draggable', 'true');
					div.append(
						dom
							.div('del')
							.html(SVGRemove)
							.on('click', () => {
								this[DATA].splice(idx, 1);
								this.render();
							})
					);
					div.on('dragover', (ev) => {
						ev.preventDefault();
					});
					div.on('dragstart', () => {
						div.addClass('draging');
						dragIdx = idx;
					});
					div.on('dragend', () => {
						div.removeClass('draging');
					});
					div.on('dragenter', () => {
						div.addClass('dropping');
					});
					div.on('dragleave', () => {
						div.removeClass('dropping');
					});
					div.on('drop', (ev) => {
						div.removeClass('dropping');
						if (idx === dragIdx) return;
						const target = this[DATA][idx];
						if (dragIdx != null) {
							const removed = this[DATA].splice(dragIdx, 1);
							let targetIndx = this[DATA].indexOf(target);
							if (dragIdx < idx) {
								targetIndx++;
							}
							this[DATA].splice(targetIndx, 0, ...removed);
							this.render();
						}
						ev.preventDefault();
					});
				}
				return div;
			}),
			this.plusButton()
		);
	}

	readonly(): boolean;
	readonly(value: boolean): this;
	readonly(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('readonly') != null;
		}
		this.attr('readonly', value);
		this.render();
		return this;
	}

	reorder(): boolean;
	reorder(value: boolean): this;
	reorder(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('reorder') != null;
		}
		this.attr('reorder', value);
		this.render();
		return this;
	}

	disabled(): boolean;
	disabled(value: boolean): this;
	disabled(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('disabled') != null;
		}
		this.attr('disabled', !!value);
		this.render();
		return this;
	}

	type(): FilesType;
	type(type: FilesType): this;
	type(type?: FilesType): FilesType | this {
		if (type === undefined) {
			return (this.attr('type') ?? 'icon') as FilesType;
		}
		this.attr('type', type);
		this.render();
		return this;
	}

	max(): number | null;
	max(len: number | null): this;
	max(len?: number | null): this | number | null {
		if (len !== undefined) {
			this.attr('max', len ? len + '' : null);
			return this;
		}
		return takeIf(this.attr('max'), (v) => parseInt(v)) ?? null;
	}

	override text(): string;
	override text(text: string): this;
	override text(text?: string): this | string | undefined {
		if (text === undefined) {
			return this[DATA].map((item) => getFileName(item)).reduce((p, name) => `${p}, ${name}`, '');
		}
		return this;
	}

	override value(): FileItem[];
	override value(value: FileItem[]): this;
	override value(value?: FileItem[]): this | FileItem[] {
		if (value === undefined) {
			return this[DATA];
		}
		this[DATA] = value;
		this.render();
		return this;
	}
}

export function files(...className: string[]) {
	return new Files().addClass(...className);
}
